package com.sukaiyi.giteeideaplugin.search.repo;

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.intellij.ide.actions.searcheverywhere.ContributorSearchResult;
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.ui.ColoredListCellRenderer;
import com.intellij.util.Processor;
import com.sukaiyi.giteeideaplugin.mainwindow.RepoDetailWindowDialogWrapper;
import com.sukaiyi.giteeideaplugin.utils.NotificationUtils;
import com.sukaiyi.giteejavaapi.common.Gitee;
import com.sukaiyi.giteejavaapi.common.Params;
import icons.PluginIcons;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;

/**
 * @author sukaiyi
 * @date 2020/07/20
 */
public class ContributorInvocationHandler implements InvocationHandler {

    private static final com.intellij.openapi.diagnostic.Logger logger = Logger.getInstance(ContributorInvocationHandler.class);

    private static final Function<String, GiteeRepoRepresentation> HTTPS_RESOLVER = url -> {
        String giteeHost = "https://gitee.com/";
        if (!url.startsWith(giteeHost)) {
            return null;
        }
        String removedPrefix = StrUtil.removePrefix(url, giteeHost);
        int markIndex = removedPrefix.indexOf('?');
        if (markIndex == -1) {
            markIndex = removedPrefix.indexOf('#');
        }
        if (markIndex != -1) {
            removedPrefix = removedPrefix.substring(0, markIndex);
        }
        removedPrefix = StrUtil.removeSuffix(removedPrefix, ".git");
        String[] parts = removedPrefix.split("/");
        if (parts.length == 2) {
            return new GiteeRepoRepresentation(parts[0], parts[1], url);
        }
        return null;
    };

    private static final Function<String, GiteeRepoRepresentation> GIT_RESOLVER = url -> {
        String giteeHost = "git@gitee.com:";
        if (!url.startsWith(giteeHost)) {
            return null;
        }
        String removedPrefix = StrUtil.removePrefix(url, giteeHost);
        int markIndex = removedPrefix.indexOf('?');
        if (markIndex == -1) {
            markIndex = removedPrefix.indexOf('#');
        }
        if (markIndex != -1) {
            removedPrefix = removedPrefix.substring(0, markIndex);
        }
        removedPrefix = StrUtil.removeSuffix(removedPrefix, ".git");
        String[] parts = removedPrefix.split("/");
        if (parts.length == 2) {
            return new GiteeRepoRepresentation(parts[0], parts[1], url);
        }
        return null;
    };

    private static Map<String, ThrowableBiFunction<Object, Object[], Object>> methodMap = null;

    public ContributorInvocationHandler() {
        methodMap = new HashMap<String, ThrowableBiFunction<Object, Object[], Object>>() {{
            put("public default java.util.List com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.getActions(java.lang.Runnable)", (proxy, args) -> Collections.emptyList());
            put("public default java.lang.String com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.filterControlSymbols(java.lang.String)", (proxy, args) -> args[0]);
            put("public default boolean com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.isMultiSelectionSupported()", (proxy, args) -> false);
            put("public default java.util.List com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.getSupportedCommands()", (proxy, args) -> Collections.emptyList());
            put("public abstract boolean com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.processSelectedItem(java.lang.Object,int,java.lang.String)", (proxy, args) -> {
                GiteeRepoRepresentation selected = (GiteeRepoRepresentation) args[0];
                Params params = Params.create().set("owner", selected.getOwner()).set("repo", selected.getRepo());
                Gitee.getInstance().invoke("getV5ReposOwnerRepo", params)
                        .whenComplete((httpResponse, throwable) -> {
                            if (throwable != null) {
                                NotificationUtils.notify(throwable.getMessage());
                                return;
                            }
                            JSONObject obj = httpResponse.asJSONObject();
                            if (!httpResponse.success()) {
                                NotificationUtils.notify(obj.getStr("message"));
                                return;
                            }
                            SwingUtilities.invokeLater(() -> new RepoDetailWindowDialogWrapper(obj).show());
                        });
                return true;
            });
            put("public abstract java.lang.String com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.getSearchProviderId()", (proxy, args) -> ContributorInvocationHandler.class.getName());
            put("public default boolean com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.isShownInSeparateTab()", (proxy, args) -> true);
            put("public abstract void com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.fetchElements(java.lang.String,com.intellij.openapi.progress.ProgressIndicator,com.intellij.util.Processor)", (proxy, args) -> {
                GiteeRepoRepresentation repo = null;
                String pattern = (String) args[0];
                if (pattern.startsWith("https://")) {
                    repo = HTTPS_RESOLVER.apply(pattern);
                } else if (pattern.startsWith("git@")) {
                    repo = GIT_RESOLVER.apply(pattern);
                }

                if (repo != null) {
                    ReflectUtil.invoke(args[2], "process", repo);
                }
                return null;
            });
            put("public default com.intellij.ide.actions.searcheverywhere.ContributorSearchResult com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.search(java.lang.String,com.intellij.openapi.progress.ProgressIndicator,int)", (proxy, args) -> {
                ContributorSearchResult.Builder<Object> builder = ContributorSearchResult.builder();
                ThrowableBiFunction<Object, Object[], Object> fetchElements = this.get("public abstract void com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.fetchElements(java.lang.String,com.intellij.openapi.progress.ProgressIndicator,com.intellij.util.Processor)");
                Integer elementsLimit = (Integer) args[3];
                fetchElements.apply(proxy, new Object[]{args[0], args[1], (Processor<Object>) o -> {
                    if (elementsLimit < 0 || builder.itemsCount() < elementsLimit) {
                        builder.addItem(o);
                        return true;
                    } else {
                        builder.setHasMore(true);
                        return false;
                    }
                }});
                return builder.build();
            });
            put("public default java.util.List com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.search(java.lang.String,com.intellij.openapi.progress.ProgressIndicator)", (proxy, args) -> {
                ThrowableBiFunction<Object, Object[], Object> fetchElements = this.get("public abstract void com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.fetchElements(java.lang.String,com.intellij.openapi.progress.ProgressIndicator,com.intellij.util.Processor)");
                List res = new ArrayList<>();
                fetchElements.apply(proxy, new Object[]{args[0], args[1], (Processor<Object>) res::add});
                return res;
            });
            put("public abstract boolean com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.showInFindResults()", (proxy, args) -> false);
            put("public default int com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.getElementPriority(java.lang.Object,java.lang.String)", (proxy, args) -> 0);
            put("public default boolean com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.isDumbAware()", (proxy, args) -> true);
            put("public abstract java.lang.String com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.getGroupName()", (proxy, args) -> "Gitee Repo");
            put("public abstract int com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.getSortWeight()", (proxy, args) -> 600);
            put("public default java.lang.String com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.getAdvertisement()", (proxy, args) -> null);
            put("public default java.lang.String com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.getFullGroupName()", (proxy, args) -> "Gitee Repo");
            put("public abstract java.lang.Object com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.getDataForItem(java.lang.Object,java.lang.String)", (proxy, args) -> null);
            put("public default boolean com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.isEmptyPatternSupported()", (proxy, args) -> false);
            put("public abstract javax.swing.ListCellRenderer com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.getElementsRenderer()", (proxy, args) -> new ColoredListCellRenderer<GiteeRepoRepresentation>() {
                @Override
                protected void customizeCellRenderer(@NotNull JList<? extends GiteeRepoRepresentation> list, GiteeRepoRepresentation value, int index, boolean selected, boolean hasFocus) {
                    setIcon(PluginIcons.GITEE);
                    append("Open ");
                    append(value.getOwner());
                    append("`s repo ");
                    append(value.getRepo());
                }
            });

            put("public default boolean com.intellij.openapi.project.PossiblyDumbAware.isDumbAware()", (proxy, args) -> false);
            put("public native int java.lang.Object.hashCode()", (proxy, args) -> ContributorInvocationHandler.this.hashCode());
            put("public boolean java.lang.Object.equals(java.lang.Object)", (proxy, args) -> proxy == args[0]);
            put("public java.lang.Class java.lang.Object.getClass()", (proxy, args) -> SearchEverywhereContributor.class);
            put("public java.lang.Object java.lang.Object.clone()", (proxy, args) -> {
                throw new CloneNotSupportedException();
            });
            put("public java.lang.String java.lang.Object.toString()", (proxy, args) -> {
                Class clazz = (Class) this.get("public java.lang.Class java.lang.Object.getClass()").apply(proxy, args);
                Integer hashCode = (Integer) this.get("public native int java.lang.Object.hashCode()").apply(proxy, args);
                return clazz.getName() + "@" + Integer.toHexString(hashCode);
            });
        }};

        methodMap.put("public abstract void com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.fetchElements(java.lang.String,boolean,com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributorFilter,com.intellij.openapi.progress.ProgressIndicator,java.util.function.Function)", (proxy, args) -> {
            ThrowableBiFunction<Object, Object[], Object> fetchElements = methodMap.get("public abstract void com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.fetchElements(java.lang.String,com.intellij.openapi.progress.ProgressIndicator,com.intellij.util.Processor)");
            return fetchElements.apply(proxy, new Object[]{args[0], args[3], (Processor<Object>) o -> ReflectUtil.invoke(args[4], "apply", o)});
        });
        methodMap.put("public abstract javax.swing.ListCellRenderer com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.getElementsRenderer(javax.swing.JList)", (proxy, args) -> {
            ThrowableBiFunction<Object, Object[], Object> fetchElements = methodMap.get("public abstract javax.swing.ListCellRenderer com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.getElementsRenderer()");
            return fetchElements.apply(proxy, args);
        });
        methodMap.put("public default boolean com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.isDumbModeSupported()", (proxy, args) -> false);
        methodMap.put("public default boolean com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor.isDumbAware()", (proxy, args) -> true);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String signature = method.toString();
        ThrowableBiFunction<Object, Object[], Object> func = methodMap.getOrDefault(signature, (p, a) -> {
            logger.info("method: " + signature + "not found, and null is returned");
            return null;
        });
        return func.apply(proxy, args);
    }

    @FunctionalInterface
    private static interface ThrowableBiFunction<T, V, R> {
        R apply(T t, V v) throws Throwable;
    }
}
